#----------------------------------
# VisualK-OS                       
#    Module: Serial Manager [v2.0] 
#    Author: Kevin Slaughter       
#----------------------------------

"""
[Revision History]
    v2.0: Standardization, misc. optimizations.
    v1.0: Initial implementation.
"""



### Lib/Header import
from VKCC_Constants import *
import globals, serial, time, os

### Obj: Comms (Serial->USB) Bridge
class cCommsBridge(object):
    ### [Functions]
    ### assertDevicePath(d): Verify whether a device path exists
    def assertDevicePath(self, sPath):
        try:
            os.stat(sPath)    # Assert path. Custom UDEV rules are needed.
            return True
        except(OSError): return False
        return
    
    
    ### checkResponses(): Check for and display any messages from each device
    def checkResponses(self):
        sResp = ""	# Buffer
        
	# FBA
	if (globals.FBA.isAvailable):
	    while (globals.FBA.serComms.inWaiting()>0):
		sResp += globals.FBA.serComms.readline()
		if '\n' in sResp: print("[FBA] checkResponses(): "+sResp); return
	else: return        

	# SynthStation
	if(globals.SynthStation.isAvailable):
		while (globals.SynthStation.serComms.inWaiting()>0):
		    sResp += globals.SynthStation.serComms.readline()
		    if '\n' in sResp:
			print("[SynthStation] checkResponses(): "+sResp); return
	else: return

        # Thor
	if (globals.Thor.isAvailable):
	        while (globals.Thor.serComms.inWaiting()>0):
	            sResp += globals.Thor.serComms.readline()
	            if '\n' in sResp: print("[Thor] checkResponses(): "+sResp); return
	else: return

    
    ### connectDevice(d): Standardized connection protocol. Takes device obj as arg
    def connectDevice(self, d):
        # Sanity check; Assert that the device exists
        if(self.assertDevicePath(d.devPath)):
            print("   [*] Found "+d.ID+" at \""+globals.FBA.devPath+"\"")
            # Open the port
            try:
                print("   - Opening COMMS port...")
                d.serComms = serial.Serial(d.devPath, 115200, parity=serial.PARITY_NONE, \
		        stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=0)
                if (d.serComms):
                    # ID received
                    print("      - Success!")
                    self.handShake(d)
                    
                    # Verify handshake
                    if (d.isHandshook): return True
                    else:
                        print("   [!] ERROR: Could not establish handshake.")
			print("     - Device disabled.\n")
                        return False
            except (OSError, serial.SerialException):
                print(serial.SerialException.message)
                return False
        else:
            print("  [!] "+d.ID+" not found at \""+d.devPath+"\"")
	    print("- Device disabled.\n")
        return


    ### handShake(): Establish handshake and ID for x-device. Gets serial instance
    def handShake(self, tDev):
        # If we got a valid handle, proceed to device ID
        if(tDev.serComms.isOpen):
            # Handshake
            bHS = False
            print("   [COMMS] Handshaking \""+tDev.ID+"\"...")
            tDev.serComms.flushInput();   tDev.serComms.flushOutput()
            print("      - Resetting... (3sec delay)") 
            tDev.serComms.setRTS(True);  time.sleep(2)      # Attempt to reset device
            tDev.serComms.setRTS(False); time.sleep(1)      # Allow for serial interface to boot
            print("      - Attempting handshake...")
            
            # Actual handshake
            sLine = ""; bIDd = False
            while not bHS:
                if (tDev.serComms.inWaiting()>0):
                    hsBuff=""; bDone=False  # Reset buffer & flag
                    
                    ### Build buffer
                    # Wait for a completed line, to avoid parsing fragments.
                    while (not bDone):
                        # Check for data in the queue... Append, if any.
                        sLine += tDev.serComms.readline()
                        
                        # If it's not empty, check it out
                        if (sLine is not ""):
                            # Check buffer for packet-terminating chars '\n' and '\r'.
                            if (('\n' in sLine) or ('\r' in sLine)):
                                sLine = sLine.rstrip('\n')     # Strip unexpected new line chars.
                                sLine = sLine.rstrip('\r')     # ^   ^   ^   ^  ^ carriage returns.
                                bDone = True
                            
                            # Display input cache
                            #if (sLine == CMD_OK): print("   [COMMS] Got CMD_OK")
                            #else: print("   [COMMS] Got data. Buffer is now \""+sLine+"\"")
                        else: print("      [COMMS] Buffer is empty... Reading again (0.5s delay)")
                        time.sleep(0.5)
                    
                    ### Process buffer
                    if (sLine is not ""):
                        # Check for ID and (if applicable) confirm handshake
                        if (sLine[:4]=="[ID]"):
                            sDevID = sLine[4:]  # Grab device ID from packet
                            print("      - Received ID string.")
                            print("      [*] Device ID'd as \""+sDevID+"\"\n         - Sending CMD_OK...")
                            sLine = ""; bIDd = True
                            
                            # Send confirmation to device and wait for response.
                            tDev.serComms.write(str(CMD_OK)+'\n');
                            tDev.serComms.flush()
                            time.sleep(0.5)
                            
                        # Got CMD_OK response. We're done!
                        elif (sLine == str(CMD_OK)):
                            if (bIDd):
                                print("      [*] Handshake successful!\n")
                                tDev.isAvailable = tDev.isHandshook = True
                                bHS = True
                                
                                # Clean-up
                                tDev.serComms.flush(); time.sleep(0.01)
                            else:
                                print("   [COMMS] Got CMD_OK, but device ID has not been received.")
                                print("      - Unknown state. Abandoning!")
                                print("      [!] Device disabled")
                                return
                        
                        # Command not recognized. Display for debugging
                        else:
                            print("   [*] Unrecognized command: '"+sLine+"'")
                            sLine = ""      # Clear buffer and try again
        return


    ### scanForSlaves(): Enumerate devices
    def scanForSlaves(self):
	# Verify device paths; Requires custom UDEV rules
        print("=====================================")
	print("[Device Scan]\n")
        # FBA
        self.connectDevice(globals.FBA)
        
        # SynthStation
    	print("  [SynthStation] Device does not exist... Literally.")
    	print("   - Device disabled.\n")
        
        # Thor
    	print("  [Thor] Assertion not yet implemented.")
	print("   - Device disabled.")
    	print("=====================================\n")
    	return
    
    
    ### sendCmd(): Send command and optional parameters to x-device
    def sendCmd(self, strDev, iCmd, iLight, iParam):
        # Setup cmd string
        sCmdStr = CMD_HEADER+"^"+str(iCmd)+"^"+str(iLight)+"^"+str(iParam)+'\n'
        
        # Send command to the specified device
	### FBA
	if (strDev == "FBA"):
	    globals.dbg("[COMMS-debug] FBA -> \""+sCmdStr+"\"")
	    if(globals.emulateSlaves):
		globals.SM.showNotification("[>>> FBA] "+sCmdStr)
		return
	    if(globals.FBA.isAvailable): globals.FBA.serComms.write(sCmdStr)

	### SynthStation
        if (strDev == "SynthStation"):
	    globals.dbg("[COMMS-debug] SynthStation -> \""+sCmdStr+"\"")
	    if(globals.emulateSlaves):
		globals.SM.showNotification("[>>> SynthStation] "+sCmdStr)
		return
	    if(SynthStation.isAvailable): globals.SynthStation.serComms.write(sCmdStr)

	### Thor
	if (strDev == "Thor"):
	    globals.dbg("[COMMS-debug] Thor -> \""+sCmdStr+"\"")
	    if(globals.emulateSlaves):
		globals.SM.showNotification(">>> [Thor] "+sCmdStr)
		return
	    if(globals.Thor.isAvailable): globals.Thor.serComms.write(sCmdStr)
        
        # Finished!
        time.sleep(0.005);	# Let serial data transmit [5ms]
        return
